package cn.bblocks.common.i18n.repository;


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import cn.bblocks.common.i18n.I18nHolder;
import cn.bblocks.common.i18n.bean.ConfigI18nMessage;
import cn.bblocks.common.i18n.config.I18nProperties;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * 国际化加载，从数据库，外部可以指定 数据源，查询SQL及主键列名称
 **/
@Slf4j
public class SimpleI18NConfigDbLoaderImpl implements I18nConfigDbLoader {
    /**
     * 同步锁
     */
    private final Lock markInvalidCodeLock = new ReentrantLock();
    private final String configTable;
    private final JdbcTemplate jdbcTemplate;

    /**
     * 插入配置表的SQL模板
     */
    private String insertSqlTemplate;

    /**
     * 查询字段的SQL
     */
    private static final String SCHEMA_SQL_TEMPLATE = "SELECT column_name FROM information_schema.COLUMNS " +
            "WHERE UPPER(TRIM(table_name)) = '%s' AND UPPER(TRIM(table_schema)) = '%s' ORDER BY ORDINAL_POSITION ASC";
    /**
     * 应用名称
     */
    @Value("${spring.application.name}")
    private String appName;

    @Resource
    private I18nProperties i18NProperties;
    @Resource
    I18nLocaleHolder i18nLocaleHolder;
    @Lazy
    @Resource
    private I18nConfigDbLoader i18nConfigDbLoader;

    /**
     * 本地db国际化资源<LanguageTag() + ":" + code,message>
     */
   private static Cache<String, String> cacheAll = Caffeine.newBuilder()
            //.expireAfterWrite(30, TimeUnit.MINUTES)//30分钟后过期
            .build();


    public SimpleI18NConfigDbLoaderImpl( String configTable,
                                         JdbcTemplate jdbcTemplate) {
        this.configTable = configTable;
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 这个配置的目的是获取i18n配置表的插入SQL模板，以便将未找到配置的code保存到表里
     */
    @PostConstruct
    public void init() {
        String tableName = i18NProperties.getConfigTable();
        this.insertSqlTemplate = "insert into `" + tableName + "` " +
                "(language,module,code, message,create_time,remark) VALUES (?, ?,?, ?,?,?) " ;

        if (log.isDebugEnabled()) {
            log.debug("i18n 配置表插入SQL模板：{}", insertSqlTemplate);
        }

        this.loadI18nDictByLocaleEntity();

    }



    /**
     * @param locale
     * @param code
     * @return
     */
    public String getLocaleCodeTxt(@NonNull  Locale locale,@NonNull String code){
       return cacheAll.getIfPresent(locale.toLanguageTag() + ":" + code);
    }


    @Override
    public void loadI18nDictByLocaleEntity() {
        if (log.isDebugEnabled()) {
            log.debug("==== 更新i18n设置 ====");
        }

        AtomicInteger count = new AtomicInteger(0);
        jdbcTemplate.query(con -> {
            PreparedStatement preparedStatement =
                    con.prepareStatement(String.format("select * from %s", configTable),
                            ResultSet.TYPE_FORWARD_ONLY,
                            ResultSet.CONCUR_READ_ONLY);
            preparedStatement.setFetchSize(200);
            preparedStatement.setFetchDirection(ResultSet.FETCH_FORWARD);
            return preparedStatement;
        }, (rs,i) -> {
                String language = rs.getString("language");
                String code = rs.getString("code");
                String message = rs.getString("message");
                String module = rs.getString("module");
                //log.info("language:{},code:{},message:{},module:{}",language,code,message,module);
                if(module == null || StrUtil.trim(module).equals("") || (Objects.equals(appName,module))){
                    cacheAll.put(language + ":"+ code, message == null ? "" : message);
                    count.addAndGet(1);
                }
                return null;
        });

        /**
         * 本地超过的部分，暂时未处理
         */
        log.info("更新i18n设置完成!,共提取数据：{}", count.get());
    }



    /**
     * 使用国际化模块自己的线程池，异步保存
     * @param code 编码
     */
    @Async("i18nExecutor")
    @Override
    public void markInvalidCode(@NonNull String code,Locale locale) {
        boolean lockResult = markInvalidCodeLock.tryLock();
        if(lockResult){
            try {
                if(locale == null){
                    locale = i18nLocaleHolder.getLocale();
                }
                if(getLocaleCodeTxt(locale,code) != null){
                    return;
                }
                log.info("记录新增国际化代码：{}，稍后请在：{} 表中翻译，并刷新缓存。", code, this.configTable);
                Object[] params = {
                        locale.toLanguageTag(),
                        this.appName,
                        code,
                        null,
                        LocalDateTime.now(),
                        "bb-i18n add"
                };
                int ret = jdbcTemplate.update(insertSqlTemplate,params);
                if(ret > 0){
                    // 插入完成后，刷新一下缓存
                    i18nConfigDbLoader.loadI18nDictByLocaleEntity();
                }
            } catch (Exception e) {
                log.warn("插入国际化代码：{} 异常：",code,e);
            }finally {
                markInvalidCodeLock.unlock();
            }
        }

    }
}
